package com.senriot.quickup.service

import cn.binarywang.wx.miniapp.api.WxMaService
import com.querydsl.core.QueryResults
import com.querydsl.jpa.impl.JPAQueryFactory
import com.senriot.quickup.domain.*
import com.senriot.quickup.domain.enums.DevStatus
import com.senriot.quickup.security.UserNotActivatedException
import com.senriot.quickup.security.WX_USER_PASSWORD
import com.senriot.quickup.security.getCurrentUserLogin
import com.senriot.quickup.security.jwt.TokenProvider
import com.senriot.quickup.service.dto.DeviceDTO
import com.senriot.quickup.service.dto.OrderDTO
import com.senriot.quickup.service.mapper.DeviceMapper
import com.senriot.quickup.service.mapper.OrderMapper
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.time.LocalDate
import kotlin.math.log

@Service
@Transactional
class MaService(private val wxMaService: WxMaService,
                private val queryFactory: JPAQueryFactory,
                private val tokenProvider: TokenProvider,
                private val deviceMapper: DeviceMapper,
                private val payService: PayService,
                private val orderMapper: OrderMapper,
                private val authenticationManagerBuilder: AuthenticationManagerBuilder)
{
    fun getToken(sessionKey: String, encryptedData: String, ivStr: String): String?
    {
        val i = wxMaService.userService.getUserInfo(sessionKey, encryptedData, ivStr)
        return if (i.unionId != null)
        {
            val u = queryFactory.selectFrom(QUser.user).where(QUser.user.unionId.eq(i.unionId)).fetchOne()
            u?.let {
                val authenticationToken = UsernamePasswordAuthenticationToken(it.login, WX_USER_PASSWORD)
                val authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken)
                SecurityContextHolder.getContext().authentication = authentication
                tokenProvider.createToken(authentication, true)
            }
        } else
        {
            null
        }
    }

    fun login(code: String): Map<String, Any?>
    {
        val session = wxMaService.jsCode2SessionInfo(code)
        var token: String? = null
        if (session.unionid != null)
        {
            val u = queryFactory.selectFrom(QUser.user).where(QUser.user.unionId.eq(session.unionid)).fetchOne()
            token = u?.let {
                val authenticationToken = UsernamePasswordAuthenticationToken(it.login, WX_USER_PASSWORD)
                val authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken)
                SecurityContextHolder.getContext().authentication = authentication
                tokenProvider.createToken(authentication, true)
            }
        }

        return mapOf(Pair("session", session), Pair("token", token))
    }

    fun getUserStatistics(unionId: String): Map<String, Any?>?
    {
        return getCurrentUserLogin().map {
            val bonus = QOrderBonus.orderBonus
            val totalAmount = queryFactory
                    .select(bonus.amount.sum())
                    .from(bonus)
                    .where(bonus.order.status.eq(1).and(bonus.order.isCashOut.isFalse).and(bonus.user.login.eq(it)))
                    .fetchOne()

            val order = QOrder.order
            val cashOut = queryFactory
                    .select(order.amount.sum())
                    .from(order)
                    .where(order.status.eq(1).and(order.isCashOut.isTrue).and(order.user.login.eq(it)))
                    .fetchOne()

            val dailyBonus = queryFactory.select(bonus.amount.sum())
                    .from(bonus)
                    .where(bonus.order.createdDate.after(LocalDate.now()
                            .atStartOfDay()).and(bonus.order.status.eq(1)).and(
                            bonus.user.login.eq(it)))
                    .fetchOne()

            val dev = QDevice.device
            val deviceCount = queryFactory.select(dev.id.count())
                    .from(dev)
                    .where(dev.bonuses.any().user.login.eq(it))
                    .fetchOne() ?: 0
            val onlineCount = queryFactory.select(dev.id.count())
                    .from(dev)
                    .where(dev.bonuses.any().user.login.eq(it).and(dev.status.eq(DevStatus.ONLINE)))
                    .fetchOne() ?: 0

//            val amount = queryFactory.select(bonus.amount.sum())
//                    .from(bonus)
//                    .where(bonus.user.login.eq(it).and(bonus.order.status.eq(1)))
//                    .fetchOne()

            mapOf(Pair("totalAmount", totalAmount ?: BigDecimal(0.00)),
                    Pair("cashOut", cashOut ?: BigDecimal(0.00)),
                    Pair("dailyBonus", dailyBonus ?: BigDecimal(0.00)),
                    Pair("deviceCount", deviceCount),
                    Pair("onlineCount", onlineCount))
        }.orElseThrow { UserNotActivatedException("用户不存在或未通过验证") }
    }

    fun getMyDevices(): List<DeviceDTO>?
    {
        return getCurrentUserLogin().map { login ->
            val dev = QDevice.device
            queryFactory.selectFrom(dev).where(dev.bonuses.any().user.login.eq(login)).fetch()
                    .map { deviceMapper.toDto(it) }
        }.orElseThrow {
            UserNotActivatedException("用户不存在或未通过验证")
        }
    }

    fun getBonus(): Map<String, BigDecimal>?
    {
        return getCurrentUserLogin().map {
            val bonus = QOrderBonus.orderBonus
            val amount = queryFactory.select(bonus.amount.sum())
                    .from(bonus)
                    .where(bonus.user.login.eq(it).and(bonus.order.status.eq(1)))
                    .fetchOne()

            val surplus = queryFactory
                    .select(bonus.amount.sum())
                    .from(bonus)
                    .where(bonus.order.status.eq(1).and(bonus.order.isCashOut.isFalse).and(bonus.user.login.eq(it)))
                    .fetchOne()

            val cashOut = queryFactory
                    .select(bonus.amount.sum())
                    .from(bonus)
                    .where(bonus.order.status.eq(1).and(bonus.order.isCashOut.isTrue).and(bonus.user.login.eq(it)))
                    .fetchOne()

            mapOf(Pair("surplus", surplus ?: BigDecimal(0.00)),
                    Pair("cashOut", cashOut ?: BigDecimal(0.00)),
                    Pair("amount", amount ?: BigDecimal(0.00)))
        }.orElseThrow { UserNotActivatedException("用户不存在或未通过验证") }
    }

    fun getOrders(unionId: String): List<OrderDTO>?
    {
        return getCurrentUserLogin().map { login ->
            val order = QOrder.order
            queryFactory.selectFrom(order)
                    .where(order.status.eq(1).and(order.bonuses.any().user.login.eq(login).or(order.user.login.eq(login))))
                    .orderBy(order.createdDate.desc())
                    .fetch().map { orderMapper.toDto(it) }
        }.orElseThrow {
            UserNotActivatedException("用户不存在或未通过验证")
        }
    }

    fun transfer(openId: String, amount: BigDecimal): MutableMap<String, Any>?
    {
        return payService.transfer(openId, amount)
    }
}
